package cn.funnymap.lgis.tools.file;

import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.file.PathUtil;
import cn.hutool.core.util.ZipUtil;
import org.mozilla.universalchardet.UniversalDetector;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * 文件工具类
 *
 * @author jiaoxn
 */
public class FileUtil {
    private FileUtil() {}

    /**
     * 获取指定文件夹的大小
     *
     * @param directory 指定的文件夹
     * @return 文件夹大小，单位：字节
     */
    public static long getDirectorySize(Path directory) throws IOException {
        long size = 0;

        try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
            for (Path file: directoryStream) {
                if (Files.isDirectory(file)) {
                    size += getDirectorySize(file);
                } else {
                    size += Files.size(file);
                }
            }
        }
        return size;
    }

    /**
     * 将指定的字节大小转为可读性更好的字符串
     *
     * @param size 文件大小，单位：字节
     * @return 可读性更好的字符串，携带单位，例如：2.3 GB
     */
    public static String readableFileSize(long size) {
        if (size <= 0) {
            return "0";
        }

        final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
        return new DecimalFormat("#,###.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
    }

    /**
     * 在指定文件夹中（包含其子级文件夹）获取指定文件扩展的文件路径
     *
     * @param targetDir 目标文件夹
     * @param extensions 指定的文件扩展
     * @return 满足检索结果的文件的文件路径
     */
    public static List<Path> getFileByFileExtension(Path targetDir, String... extensions) throws IOException {
        List<Path> response = new ArrayList<>();

        try (DirectoryStream<Path> stream = Files.newDirectoryStream(targetDir, entry -> {
            if (Files.isDirectory(entry)) {
                response.addAll(getFileByFileExtension(entry, extensions));
            } else {
                String fileName = entry.getFileName().toString();
                for (String extension : extensions) {
                    if (fileName.endsWith(extension)) {
                        return true;
                    }
                }
            }

            return false;
        })) {
            for (Path file: stream) {
                response.add(file);
            }
        }

        return response;
    }

    /**
     * 在指定文件夹中（包含其子级文件夹）获取指定名称的文件路径
     *
     * @param targetDir 目标文件夹
     * @param fileName 指定的文件名称
     * @param isNested 是否嵌套子级文件夹查询
     * @return 满足检索结果的文件的文件路径
     */
    public static List<Path> getFileByFileName(Path targetDir, String fileName, boolean isNested) throws IOException {
        List<Path> foundFiles = new ArrayList<>();

        if (Files.isDirectory(targetDir)) {
            try (DirectoryStream<Path> stream = Files.newDirectoryStream(targetDir, entry -> {
                if (Files.isDirectory(entry) && isNested) {
                    foundFiles.addAll(getFileByFileName(entry, fileName, true));
                } else {
                    // 如果找到匹配的文件名，则添加到结果列表中
                    if (entry.getFileName().toString().equalsIgnoreCase(fileName)) {
                        foundFiles.add(entry);
                    }
                }

                return false;
            })) {
                for (Path file : stream) {
                    foundFiles.add(file);
                }
            }
        }

        return foundFiles;
    }
    public static List<Path> getFileByFileName(Path targetDir, List<String> fileNameList, boolean isNested) throws IOException {
        List<Path> foundFiles = new ArrayList<>();

        for (String fileName : fileNameList) {
            List<Path> temp = getFileByFileName(targetDir, fileName, isNested);
            foundFiles.addAll(temp);
        }

        return foundFiles;
    }

    /**
     * 获取指定文件的文件扩展名
     *
     * @param path 目标文件
     * @return 文件的扩展名，例如：.zip、.txt
     */
    public static String getFileExtension(Path path) {
        // 如果是文件夹，返回null
        if (Files.isDirectory(path)) {
            return null;
        }

        String fileName = path.getFileName().toString();
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex != -1 && dotIndex < fileName.length() - 1) {
            return fileName.substring(dotIndex);
        }

        return null;
    }

    /**
     * 从文件名称中获取文件扩展名
     *
     * @param fileName 文件名称
     * @return 文件扩展名
     */
    public static String getFileExtension(String fileName) {
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex != -1 && dotIndex < fileName.length() - 1) {
            return fileName.substring(dotIndex);
        }

        return null;
    }

    /**
     * 获取不带有文件扩展的文件名
     *
     * @param path 目标文件
     * @return 不带文件扩展的文件名，例如：对于“测试.zip”返回“测试”
     */
    public static String getFileNameWithoutExtension(Path path) {
        if (Files.isDirectory(path)) {
            return null;
        }

        String fileName = path.getFileName().toString();
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex != -1) {
            return fileName.substring(0, dotIndex);
        }
        return fileName;
    }

    /**
     * 将ZIP文件解压到临时目录
     *
     * @param zipFilePath 需要解压的ZIP文件
     * @return 解压后的临时目录，如果输入的数据不是ZIP文件夹，则返回null
     */
    public static Path extractZipFileToTemp(Path zipFilePath) throws IOException {
        // 如果不是ZIP文件，则直接返回null
        if (!validateFileType(zipFilePath, FileType.ZIP)) {
            return null;
        }

        String fileName = getFileNameWithoutExtension(zipFilePath);
        Path tempDir = Files.createTempDirectory(fileName);

        // 解压ZIP文件到临时目录
        extractZipFile(zipFilePath, tempDir);

        return tempDir;
    }

    public static void extractZipFile(Path zipFilePath, Path targetDir) {
        // 如果不是ZIP文件，则直接返回null
        if (!validateFileType(zipFilePath, FileType.ZIP)) {
            return;
        }

        try {
            try (ZipInputStream zipInputStream =
                         new ZipInputStream(Files.newInputStream(zipFilePath), Charset.forName("GBK"))) {
                ZipEntry zipEntry = zipInputStream.getNextEntry();

                while (zipEntry != null) {
                    Path filePath = Paths.get(targetDir.toAbsolutePath().toString(), zipEntry.getName());
                    if (zipEntry.isDirectory()) {
                        Files.createDirectories(filePath);
                    } else {
                        Files.createDirectories(filePath.getParent());
                        Files.copy(zipInputStream, filePath);
                    }

                    zipInputStream.closeEntry();
                    zipEntry = zipInputStream.getNextEntry();
                }
            }
        } catch (IOException ioException) {
            throw new RuntimeException("ZIP压缩包解压异常");
        }
    }

    public static Path compressDirAsZip(Path dirPath) {
        if (Files.isDirectory(dirPath)) {
            Path zipFile = dirPath.getParent().resolve(String.format("%s%s", dirPath.getFileName(), ".zip"));
            ZipUtil.zip(zipFile.toFile(), dirPath.isAbsolute());
            return zipFile;
        }

        return null;
    }

    public static boolean validateFileType(String fileName, FileType targetFileType) {
        String fileExtension = getFileExtension(fileName);
        return targetFileType.getValue().equalsIgnoreCase(fileExtension);
    }
    public static boolean validateFileType(Path path, FileType targetFileType) {
        String fileExtension = getFileExtension(path);
        return targetFileType.getValue().equalsIgnoreCase(fileExtension);
    }
    public static boolean validateFileType(Path path, List<FileType> targetFileTypeList) {
        String fileExtension = getFileExtension(path);
        return targetFileTypeList.stream().anyMatch(item -> item.getValue().equalsIgnoreCase(fileExtension));
    }

    public static void delete(Path path) throws IORuntimeException {
        if (path != null) {
            PathUtil.del(path);
        }
    }

    public static String detectEncoding(Path path) throws IOException {
        byte[] buf = new byte[4096];
        try (InputStream inputStream = Files.newInputStream(path)) {
            UniversalDetector detector = new UniversalDetector();
            int nRead;
            while ((nRead = inputStream.read(buf)) > 0 && !detector.isDone()) {
                detector.handleData(buf, 0, nRead);
            }
            detector.isDone();

            String encoding = detector.getDetectedCharset();
            if (encoding == null) {
                encoding = "utf-8";
            }
            detector.reset();

            return encoding;
        }
    }

    /**
     * 在指定文件夹中（包含其子级文件夹）获取指定名称的文件路径
     *
     * @param targetDir 目标文件夹
     * @param fileName 指定的文件名称
     * @param isNested 是否嵌套子级文件夹查询
     * @return 满足检索结果的文件的文件路径
     */
    public static List<Path> getFileByFuzzyMatchingFilename(Path targetDir, String fileName, boolean isNested) throws IOException {
        List<Path> foundFiles = new ArrayList<>();

        if (Files.isDirectory(targetDir)) {
            try (DirectoryStream<Path> stream = Files.newDirectoryStream(targetDir, entry -> {
                if (Files.isDirectory(entry) && isNested) {
                    foundFiles.addAll(getFileByFileName(entry, fileName, true));
                } else {
                    // 如果找到匹配的文件名，则添加到结果列表中
                    if (entry.getFileName().toString().startsWith(fileName)) {
                        foundFiles.add(entry);
                    }
                }

                return false;
            })) {
                for (Path file : stream) {
                    foundFiles.add(file);
                }
            }
        }

        return foundFiles;
    }
}
